/*

  event.c: Implements the event synchronization primative. Processes
  can open events, wait on events, and signal events. For more information,
  see the README.

  Ben Marks, Chris Lekas
  February 2014

 */

/* Some Useful Includes */
#include <linux/kernel.h>   // can only be included at kernel-level
#include <asm/uaccess.h>    
#include <linux/syscalls.h> 
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/delay.h>

/* What follows should only be accessible in the kernel */
#ifdef __KERNEL__

// Max number of events possible on the system
#define MAXEVENTS 5

/* Events are stored in an array of event structs */
struct Event {
  int               inUse;      // Active event?
  int               id;         // User level event ID
  wait_queue_head_t waitHead;   // Waiters on event
  struct list_head  openPids;   // People with event open
  int               numWaiters; // How many waiters?
  int               numOpen;    // How many open?
  struct semaphore  lock;       // Mutex for accessing Event data fields

};


/* 
   Each process that has the event open is associated with
   a unique OpenProc struct. 
*/
struct OpenProc {
  pid_t pid;              // Pid that has event open
  struct list_head next;  // Keep all open Pi's in a list
};

/* Mutex protecting the array of events during opening and 
   closing of events. 
*/
static DECLARE_MUTEX( table_mutex );

/*
  Global data structure of all event structs 
*/
static struct Event table[MAXEVENTS];

#endif /* __KERNEL__ */



/* Finds an empty event slot in the array of events.

   Arguments: none

   Returns: -1 if there was a problem waiting.
            -2 if there are no more event slots left.
	  >  0 ID of free event slot
*/
static int getValidID(void) {
  
  int i;
  // Lock the table
  if ( down_interruptible( &table_mutex ) ) {
    return -1;
  }

  // Find a free event slot
  for ( i = 0; i < MAXEVENTS; i++ ) {
    if ( ! table[i].inUse ) {
      table[i].inUse = 1;
      table[i].id = i+1;
      /* Don't deadlock! */
      up( &table_mutex );
      // Return the ID (which is 1 more than the index)
      return i+1;
    }
  }
  
  /* No event slots left */
  up( &table_mutex );
  return -2;

}

/* Checks if an event is already in use (and thus valid
   for a call to any interface function other than print.

   Arguments: id - ID of event to check
              closing - 0 if we are checking for a valid argument
	                  to event_open(); 1 otherwise.

   Returns: 0 if event ID is valid and already in use
           -1 if problem waiting on event lock
	   -2 if invalid index
*/
static int checkValidID( int id, int closing ) {
  
  int toRet; 
  int pidHasOpened;
  int curPid;
  struct OpenProc *tmp;
  struct OpenProc *next;

  /* Sanity check on ID values */
  if ( id <= 0 ) {
    return -2;
  }
  if ( id > MAXEVENTS ) {
    return -2;
  }

  /* Lock the table to prevent other modificaitons */
  if ( down_interruptible( &table_mutex ) ) {
    return -1;
  }

  /* Is this slot open by anybody ? */
  if ( table[id-1].inUse ) {

    curPid = get_current()->pid;
    pidHasOpened = 0;
    /* Check if the process has opened this alread */
    list_for_each_entry_safe( tmp, next, &(table[id-1].openPids), next ) {
      if ( tmp->pid == curPid ) {
	pidHasOpened = 1;
	break;
      }
    }
    /* Valid combinations are:
       -> We're closing this event, and we have already opened
          it.
       -> We're opening this event and we have not already 
          opened it.
    */
    if ( pidHasOpened && closing ) {
      toRet =  0;
    }
    else if ( !pidHasOpened &&!closing ) {
      toRet =  0;
    }
    else {
      // Something's wrong. Either closing a non-opened, or 
      // opening a second time
      toRet = -2;
    }
  }
  else {
    // Trying to open an event ID that doesn't exist.
    toRet = -2;
  }

  /* Don't deadlock! */
  up( &table_mutex );

  return toRet;

}

/*
  Adds the current process to the event associated with ID id.

  Arguments: id - ID of event to add process to

   Returns: 0 if completed successfully
           -1 if problem waiting on event lock
	   -2 if kmalloc failed 
*/
static int addToEvent( int id ) {

  int idx;
  struct Event * e;
  struct OpenProc * me;

  // Convert ID to index in table
  idx = id - 1;

  // Grab the lock for the event
  e = &table[idx];
  if ( down_interruptible( &(e->lock) ) ) {
    return -1;
  }
  
  // Create a new OpenProc struct
  me = (struct OpenProc *) kmalloc( sizeof( struct OpenProc ), GFP_KERNEL );
  if ( ! me ) {
    // kmalloc failed
    return -2;
  }

  // Set the struct values properly
  me->pid = get_current()->pid;
  list_add_tail( &(me->next), &(e->openPids));

  // Keep track of number of open users
  e->numOpen ++;

  // Don't deadlock
  up( &(e->lock) );

  // Successful
  return 0;

}

/*
  Remove the current process from the event associated with ID id.

  Arguments: id - ID of event to remove process from

   Returns: 0 if completed successfully
           -1 if problem waiting on event lock
	   
*/
static int removeFromEvent( int id ) {

  int idx;
  int curPid;
  struct Event * e;
  struct OpenProc *tmp;
  struct OpenProc *next;
  
  // Convert ID to idx in table
  idx = id - 1;

  // Grab the lock
  e = &table[idx];
  if ( down_interruptible( &(e->lock) ) ) {
    return -1;
  }
  
  // Find the OpenProc struct to remove
  // Note that this must exist since we check before calling this
  // And we would never call removeFromEvent twice
  curPid = get_current()->pid ;
  list_for_each_entry_safe( tmp, next, &(table[idx].openPids), next ) {
    if ( tmp->pid == curPid ) {
      break;
    }
  }

  /* tmp is the structure we want to remove */
  list_del( &(tmp->next) );

  /* Don't leak kernel memory */
  kfree( tmp );

  /* Check if we're the last process waiting on the event */
  e->numOpen --;
  if ( e->numOpen == 0 ) {
    e->inUse = 0;
  }

  // Don't deadlock
  up( &(e->lock) );

  // Success
  return 0;

}

/*
  print_event_table: Prints out current usage of the event table,
  including the PIDs that are open on / waiting on each event.

  VERY useful for debugging. Or, if you're just curious about
  what's happening at the Kernel level. :)

  Arguments: None

  Returns: 0 on success
          -1 if error and sets ERRNO
 */
SYSCALL_DEFINE0( print_event_table ) {
  
  int i;
  struct OpenProc *tmp;
  struct OpenProc *next;
  struct list_head *tmp2;
  struct list_head *next2;

  // Make logs easier to read
  printk("\n\n");

  // Lock the entire table, so no new events created while we're 
  // printing
  if ( down_interruptible( &table_mutex ) ) {
    return -EBUSY;
  }

  // Print all events
  for ( i = 0; i < MAXEVENTS; i++ ) {
     /* Iterate using indices */
    struct Event * e = &table[i];
    // Grab the lock
    if ( down_interruptible( &(e->lock) ) ) {
      up( &table_mutex );
      return -EBUSY;
    }
    
    // Is this event open by somebody? 
    printk("Event Information for ID %d\n", e->id);
    if ( e->inUse ) {
      printk("    Event is in use.\n");
    }
    else {
      printk("    Event not in use.\n");
      up( &(e->lock) );
      continue;
    }

    // PIDS with this event open
    printk("    %d PIDs with this event open: ", e->numOpen);
    list_for_each_entry_safe( tmp, next, &(e->openPids), next ) {
      printk(" %d ", (int) tmp->pid);
    }
    printk("\n");


    // PIDS waiting on this event
    printk("    %d PIDs waiting on this event: ", e->numWaiters);
    list_for_each_safe( tmp2, next2, &(e->waitHead.task_list)) {
      wait_queue_t * curr = list_entry(tmp2, wait_queue_t, task_list);
      struct task_struct* ts = curr->private;
      printk(" %d ", (int) ts->pid);
    }
    printk("\n");
    
    // Done with this task
    up( &(e->lock) );

  }
  // Done with the table
  up( &table_mutex );

  // Success
  return 0;

}

/*
  event_open: Open a new event or existing event

  Arguments: int * id - A pointer to the ID value from the user
             0 - "Give me a new event"
	    >0 - "Give me an existing event"

  Returns 0 on success, or -1 on failure, setting ERRNO appropriately. 
 */
SYSCALL_DEFINE1( event_open, int __user *, id ) {

  int kerID;
  int retVal;

  /* Check for bogus pointers */
  if ( !access_ok(VERIFY_WRITE, id, sizeof(int)) ) {
    return -EFAULT;
  }
  if ( copy_from_user(&kerID, id, sizeof(int) ) ) {
    return -EFAULT;
  }
  /* Sanity check on the ID value */
  if ( kerID < 0 ) {
    return -EINVAL;
  }
  
  if ( kerID == 0 ) {
    /* We get to assign a new ID */
    kerID = getValidID();
    if ( kerID == -1 ) {
      /* Error while waiting */
      return -EBUSY;
    }
    if ( kerID == -2 ) { 
      // No more events - table full
      return -ENOBUFS;
    }
    /* Idx is the index of the event struct we want to access */
    // Initialize the struct at table[idx], locking it first. 
    if ( retVal = (addToEvent ( kerID )) ) {
      if ( retVal == -2 ) {
	return -ENOMEM; // kmalloc failed
      }
      return -EBUSY; // Problem getting lock
    }
  }
  else {
    // User specified an existing ID
    retVal = checkValidID( kerID, 0 );
    if ( retVal == -1 ) {
      /* Error while waiting */
      return -EBUSY;
    }
    if ( retVal == -2 ) { 
      return -EINVAL; // Bad ID
    }
    if ( addToEvent( kerID ) ) {
      return -EBUSY; // Error locking
    }
  }
  
  // Set the ID pointer value with the Event ID
  if ( copy_to_user(id, &kerID, sizeof(int) ) ) {
    return -EFAULT;
  }

  // Success
  return 0;

}

/*
  event_close: Close an event and free the OpenProc struct.

  Arguments: int id - ID of event to close

  Returns 0 on success, -1 on failure and sets ERRNO appropriately.
  
 */
SYSCALL_DEFINE1( event_close, int, id ) {

  int retVal;

  printk("In event_close\n");
  
  // Does the PID have this event open already?
  retVal = checkValidID( id, 1 );
  if ( retVal == -2 ) {
    return -EINVAL; // Nope!
  }
  else if ( retVal == -1 ) {
    return -EBUSY; // Problem locking
  }
  
  /* This process has indeed opened the event. */
  if ( removeFromEvent( id ) ) {
    return -EBUSY; // Problem locking
  }

  // Success
  return 0;

}

/*
  event_wait: Block current process on the event with ID id.

  Arguments: int id - ID of event to block on.

  Returns 0 on success, -1 on failure and sets ERRNO appropriately.

 */
SYSCALL_DEFINE1( event_wait, int, id ) {

  struct task_struct* ts;
  int retVal, idx;
  struct Event* e;

  // Determine table index from ID
  idx = id - 1;

  // Get our current task struct
  ts = get_current();

  printk("PID %d in event_wait \n", (int)ts->pid);

  // Check if we've opened this event
  retVal = checkValidID( id, 1 );
  if ( retVal == -2 ) {
    return -EINVAL; // Nope!
  }
  else if ( retVal == -1 ) {
    return -EBUSY; // Problem locking
  }

  // Lock the event
  e = &table[idx];
  if ( down_interruptible( &(e->lock) ) ) {
    return -EBUSY;
  }
  
  // Set up the state for blocking soon
  ts->state = TASK_INTERRUPTIBLE;
  DECLARE_WAITQUEUE( wait_entry, ts );
  add_wait_queue( &(e->waitHead), &wait_entry );
  
  // Keep track of number of waiters
  e->numWaiters++;

  // Unlock the event
  up( &(e->lock) );

  printk("PID %d preparing to block \n", (int)ts->pid);

  /* Prepare to block . . . */
  schedule();

  /* And . . . we're back! */

  // Grab the lock again
  if ( down_interruptible( &(e->lock) ) ) {
    return -EBUSY;
  }
  // Take us off the wait queue, since we're done
  remove_wait_queue( &(e->waitHead), &wait_entry );

  // And, we're not waiting anymore
  e->numWaiters--;

  // Release the lock
  up( &(e->lock) );

  printk("PID %d just woke up!\n", (int)ts->pid);

  // Success
  return 0;

}

/*
  event_signal: Signal all waiters on an event to wake up.

  Arguments: int id - ID of event to wake up.

  Returns 0 on success, -1 on failure and sets ERRNO appropriately.

  
 */
SYSCALL_DEFINE1( event_signal, int, id ) {
  
  int retVal, idx;
  struct Event* e;
  
  // Get table index from ID
  idx = id - 1;

  printk("In event_signal for event %d\n", id);

  // Check if the process has opened the event it wants to signal
  retVal = checkValidID( id, 1 );
  if ( retVal == -2 ) {
    return -EINVAL; // Nope!
  }
  else if ( retVal == -1 ) {
    return -EBUSY; // Error locking
  }

  // Lock the event
  e = &table[idx];
  if ( down_interruptible( &(e->lock) ) ) {
    return -EBUSY;
  }
  
  printk("Preparing to wake up for event %d\n", id);
  /* Rise and Shine! */
  wake_up(&(e->waitHead));

  
  printk("Woke up! for event %d\n", id);

  // Release the lock
  up( &(e->lock) );

  return 0;

}

/*
  event_init: State initialization for events at boot
  
  Arguments - None

  Returns 0 always
 */
static int __init event_init(void) {
  int i;
  struct Event * e;

  // For each event
  for (i = 0; i < MAXEVENTS; i++) {
    printk("Initializing Event %d\n", i);
    msleep(1000);
    e = &table[i];
    e->inUse = 0; // Nobody's using it
    init_waitqueue_head( &(e->waitHead) ); // Set up the waiters queue
    INIT_LIST_HEAD( &(e->openPids) );      // Set up the list of open
    e->numWaiters = 0; // Nobody's waiting yet
    e->numOpen = 0;    // And nobody's opened it yet
    e->id = i+1; // ID is one more than table index
    init_MUTEX( &e->lock ); // Set up event lock
  }

  // Success!
  return 0;

}


/*
  closeEvents - Close all open events on current process

  Declared in /linux/event.h

  Iterates through all events and closes any open events of
  an exiting process. This way, if a process is killed, we don't
  leak event slots or memory. 
 */
void closeEvents( void ) {
  int retVal;
  int id;

  for ( id = 1; id <= 5; id++ ) {
    
    // Does the PID have this event open already?
    retVal = checkValidID( id, 1 );
    if ( retVal == -2 ) {
      continue;
    }
    else if ( retVal == -1 ) {
      continue; 
    }
  
    /* This process has indeed opened the event. */
    if ( removeFromEvent( id ) ) {
      printk("Error Closing Event %d by exiting pid %d\n",
	     id, (int)get_current()->pid);
    }
  }
  
}



/* Run event_init at boot */
pure_initcall(event_init);
